home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 February (DVD) / PCWorld_2008-02_DVD.iso / v cisle / PHP / PHP.exe / xampp-win32-1.6.5-installer.exe / php / PEAR / Net / Sieve.php < prev    next >
Encoding:
PHP Script  |  2007-12-20  |  37.1 KB  |  1,146 lines

  1. <?php
  2. // +-----------------------------------------------------------------------+
  3. // | Copyright (c) 2002-2003, Richard Heyes                                |
  4. // | Copyright (c) 2006, Anish Mistry                                      |
  5. // | All rights reserved.                                                  |
  6. // |                                                                       |
  7. // | Redistribution and use in source and binary forms, with or without    |
  8. // | modification, are permitted provided that the following conditions    |
  9. // | are met:                                                              |
  10. // |                                                                       |
  11. // | o Redistributions of source code must retain the above copyright      |
  12. // |   notice, this list of conditions and the following disclaimer.       |
  13. // | o Redistributions in binary form must reproduce the above copyright   |
  14. // |   notice, this list of conditions and the following disclaimer in the |
  15. // |   documentation and/or other materials provided with the distribution.|
  16. // | o The names of the authors may not be used to endorse or promote      |
  17. // |   products derived from this software without specific prior written  |
  18. // |   permission.                                                         |
  19. // |                                                                       |
  20. // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |
  21. // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |
  22. // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
  23. // | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |
  24. // | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
  25. // | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |
  26. // | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
  27. // | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
  28. // | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |
  29. // | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
  30. // | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |
  31. // |                                                                       |
  32. // +-----------------------------------------------------------------------+
  33. // | Author: Richard Heyes <richard@phpguru.org>                           |
  34. // | Co-Author: Damian Fernandez Sosa <damlists@cnba.uba.ar>               |
  35. // | Co-Author: Anish Mistry <amistry@am-productions.biz>                  |
  36. // +-----------------------------------------------------------------------+
  37.  
  38. require_once('Net/Socket.php');
  39.  
  40. /**
  41. * TODO
  42. *
  43. * o supportsAuthMech()
  44. */
  45.  
  46. /**
  47. * Disconnected state
  48. * @const NET_SIEVE_STATE_DISCONNECTED
  49. */
  50. define('NET_SIEVE_STATE_DISCONNECTED',  1, true);
  51.  
  52. /**
  53. * Authorisation state
  54. * @const NET_SIEVE_STATE_AUTHORISATION
  55. */
  56. define('NET_SIEVE_STATE_AUTHORISATION', 2, true);
  57.  
  58. /**
  59. * Transaction state
  60. * @const NET_SIEVE_STATE_TRANSACTION
  61. */
  62. define('NET_SIEVE_STATE_TRANSACTION',   3, true);
  63.  
  64. /**
  65. * A class for talking to the timsieved server which
  66. * comes with Cyrus IMAP.
  67. *
  68. * SIEVE: RFC3028 http://www.ietf.org/rfc/rfc3028.txt
  69. * MANAGE-SIEVE: http://www.ietf.org/internet-drafts/draft-martin-managesieve-06.txt
  70. *
  71. * @author  Richard Heyes <richard@php.net>
  72. * @author  Damian Fernandez Sosa <damlists@cnba.uba.ar>
  73. * @author  Anish Mistry <amistry@am-productions.biz>
  74. * @access  public
  75. * @version 1.2.0
  76. * @package Net_Sieve
  77. */
  78.  
  79. class Net_Sieve
  80. {
  81.     /**
  82.     * The socket object
  83.     * @var object
  84.     */
  85.     var $_sock;
  86.  
  87.     /**
  88.     * Info about the connect
  89.     * @var array
  90.     */
  91.     var $_data;
  92.  
  93.     /**
  94.     * Current state of the connection
  95.     * @var integer
  96.     */
  97.     var $_state;
  98.  
  99.     /**
  100.     * Constructor error is any
  101.     * @var object
  102.     */
  103.     var $_error;
  104.  
  105.     /**
  106.     * To allow class debuging
  107.     * @var boolean
  108.     */
  109.     var $_debug = false;
  110.  
  111.     /**
  112.     * Allows picking up of an already established connection
  113.     * @var boolean
  114.     */
  115.     var $_bypassAuth = false;
  116.  
  117.     /**
  118.     * Whether to use TLS if available
  119.     * @var boolean
  120.     */
  121.     var $_useTLS = true;
  122.  
  123.     /**
  124.     * The auth methods this class support
  125.     * @var array
  126.     */
  127.     var $supportedAuthMethods=array('DIGEST-MD5', 'CRAM-MD5', 'PLAIN' , 'LOGIN');
  128.     //if you have problems using DIGEST-MD5 authentication  please comment the line above and uncomment the following line
  129.     //var $supportedAuthMethods=array( 'CRAM-MD5', 'PLAIN' , 'LOGIN');
  130.  
  131.     //var $supportedAuthMethods=array( 'PLAIN' , 'LOGIN');
  132.  
  133.     /**
  134.     * The auth methods this class support
  135.     * @var array
  136.     */
  137.     var $supportedSASLAuthMethods=array('DIGEST-MD5', 'CRAM-MD5');
  138.  
  139.     /**
  140.     * Handles posible referral loops
  141.     * @var array
  142.     */
  143.     var $_maxReferralCount = 15;
  144.  
  145.     /**
  146.     * Constructor
  147.     * Sets up the object, connects to the server and logs in. stores
  148.     * any generated error in $this->_error, which can be retrieved
  149.     * using the getError() method.
  150.     *
  151.     * @param  string $user      Login username
  152.     * @param  string $pass      Login password
  153.     * @param  string $host      Hostname of server
  154.     * @param  string $port      Port of server
  155.     * @param  string $logintype Type of login to perform
  156.     * @param  string $euser     Effective User (if $user=admin, login as $euser)
  157.     * @param  string $bypassAuth Skip the authentication phase.  Useful if the socket
  158.                                   is already open.
  159.     * @param  boolean $useTLS Use TLS if available
  160.     */
  161.     function Net_Sieve($user = null , $pass  = null , $host = 'localhost', $port = 2000, $logintype = '', $euser = '', $debug = false, $bypassAuth = false, $useTLS = true)
  162.     {
  163.         $this->_state = NET_SIEVE_STATE_DISCONNECTED;
  164.         $this->_data['user'] = $user;
  165.         $this->_data['pass'] = $pass;
  166.         $this->_data['host'] = $host;
  167.         $this->_data['port'] = $port;
  168.         $this->_data['logintype'] = $logintype;
  169.         $this->_data['euser'] = $euser;
  170.         $this->_sock = &new Net_Socket();
  171.         $this->_debug = $debug;
  172.         $this->_bypassAuth = $bypassAuth;
  173.         $this->_useTLS = $useTLS;
  174.         /*
  175.         * Include the Auth_SASL package.  If the package is not available,
  176.         * we disable the authentication methods that depend upon it.
  177.         */
  178.         if ((@include_once 'Auth/SASL.php') === false) {
  179.             if($this->_debug){
  180.                 echo "AUTH_SASL NOT PRESENT!\n";
  181.             }
  182.             foreach($this->supportedSASLAuthMethods as $SASLMethod){
  183.                 $pos = array_search( $SASLMethod, $this->supportedAuthMethods );
  184.                 if($this->_debug){
  185.                     echo "DISABLING METHOD $SASLMethod\n";
  186.                 }
  187.                 unset($this->supportedAuthMethods[$pos]);
  188.             }
  189.         }
  190.         if( ($user != null) && ($pass != null) ){
  191.             $this->_error = $this->_handleConnectAndLogin();
  192.         }
  193.     }
  194.  
  195.     /**
  196.     * Handles the errors the class can find
  197.     * on the server
  198.     *
  199.     * @access private
  200.     * @param mixed $msg  Text error message or PEAR error object
  201.     * @param integer $code  Numeric error code
  202.     * @return PEAR_Error
  203.     */
  204.     function _raiseError($msg, $code)
  205.     {
  206.         include_once 'PEAR.php';
  207.         return PEAR::raiseError($msg, $code);
  208.     }
  209.  
  210.     /**
  211.     * Handles connect and login.
  212.     * on the server
  213.     *
  214.     * @access private
  215.     * @return mixed Indexed array of scriptnames or PEAR_Error on failure
  216.     */
  217.     function _handleConnectAndLogin()
  218.     {
  219.         if (PEAR::isError($res = $this->connect($this->_data['host'] , $this->_data['port'], null, $this->_useTLS ))) {
  220.             return $res;
  221.         }
  222.         if($this->_bypassAuth === false) {
  223.            if (PEAR::isError($res = $this->login($this->_data['user'], $this->_data['pass'], $this->_data['logintype'] , $this->_data['euser'] , $this->_bypassAuth) ) ) {
  224.                 return $res;
  225.             }
  226.         }
  227.         return true;
  228.     }
  229.  
  230.     /**
  231.     * Returns an indexed array of scripts currently
  232.     * on the server
  233.     *
  234.     * @return mixed Indexed array of scriptnames or PEAR_Error on failure
  235.     */
  236.     function listScripts()
  237.     {
  238.         if (is_array($scripts = $this->_cmdListScripts())) {
  239.             $this->_active = $scripts[1];
  240.             return $scripts[0];
  241.         } else {
  242.             return $scripts;
  243.         }
  244.     }
  245.  
  246.     /**
  247.     * Returns the active script
  248.     *
  249.     * @return mixed The active scriptname or PEAR_Error on failure
  250.     */
  251.     function getActive()
  252.     {
  253.         if (!empty($this->_active)) {
  254.             return $this->_active;
  255.  
  256.         } elseif (is_array($scripts = $this->_cmdListScripts())) {
  257.             $this->_active = $scripts[1];
  258.             return $scripts[1];
  259.         }
  260.     }
  261.  
  262.     /**
  263.     * Sets the active script
  264.     *
  265.     * @param  string $scriptname The name of the script to be set as active
  266.     * @return mixed              true on success, PEAR_Error on failure
  267.     */
  268.     function setActive($scriptname)
  269.     {
  270.         return $this->_cmdSetActive($scriptname);
  271.     }
  272.  
  273.     /**
  274.     * Retrieves a script
  275.     *
  276.     * @param  string $scriptname The name of the script to be retrieved
  277.     * @return mixed              The script on success, PEAR_Error on failure
  278.     */
  279.     function getScript($scriptname)
  280.     {
  281.         return $this->_cmdGetScript($scriptname);
  282.     }
  283.  
  284.     /**
  285.     * Adds a script to the server
  286.     *
  287.     * @param  string $scriptname Name of the script
  288.     * @param  string $script     The script
  289.     * @param  boolean $makeactive Whether to make this the active script
  290.     * @return mixed              true on success, PEAR_Error on failure
  291.     */
  292.     function installScript($scriptname, $script, $makeactive = false)
  293.     {
  294.         if (PEAR::isError($res = $this->_cmdPutScript($scriptname, $script))) {
  295.             return $res;
  296.  
  297.         } elseif ($makeactive) {
  298.             return $this->_cmdSetActive($scriptname);
  299.  
  300.         } else {
  301.             return true;
  302.         }
  303.     }
  304.  
  305.     /**
  306.     * Removes a script from the server
  307.     *
  308.     * @param  string $scriptname Name of the script
  309.     * @return mixed              True on success, PEAR_Error on failure
  310.     */
  311.     function removeScript($scriptname)
  312.     {
  313.         return $this->_cmdDeleteScript($scriptname);
  314.     }
  315.  
  316.     /**
  317.     * Returns any error that may have been generated in the
  318.     * constructor
  319.     *
  320.     * @return mixed False if no error, PEAR_Error otherwise
  321.     */
  322.     function getError()
  323.     {
  324.         return PEAR::isError($this->_error) ? $this->_error : false;
  325.     }
  326.  
  327.     /**
  328.     * Handles connecting to the server and checking the
  329.     * response is valid.
  330.     *
  331.     * @access private
  332.     * @param  string $host Hostname of server
  333.     * @param  string $port Port of server
  334.     * @param  array  $options List of options to pass to connect
  335.     * @param  boolean $useTLS Use TLS if available
  336.     * @return mixed        True on success, PEAR_Error otherwise
  337.     */
  338.     function connect($host, $port, $options = null, $useTLS = true)
  339.     {
  340.         if (NET_SIEVE_STATE_DISCONNECTED != $this->_state) {
  341.             $msg='Not currently in DISCONNECTED state';
  342.             $code=1;
  343.             return $this->_raiseError($msg,$code);
  344.         }
  345.  
  346.         if (PEAR::isError($res = $this->_sock->connect($host, $port, false, 5, $options))) {
  347.             return $res;
  348.         }
  349.  
  350.         if($this->_bypassAuth === false) {
  351.             $this->_state = NET_SIEVE_STATE_AUTHORISATION;
  352.             if (PEAR::isError($res = $this->_doCmd())) {
  353.                 return $res;
  354.             }
  355.         } else {
  356.             $this->_state = NET_SIEVE_STATE_TRANSACTION;
  357.         }
  358.  
  359.         // Explicitly ask for the capabilities in case the connection
  360.         // is picked up from an existing connection.
  361.         if(PEAR::isError($res = $this->_cmdCapability() )) {
  362.             $msg='Failed to connect, server said: ' . $res->getMessage();
  363.             $code=2;
  364.             return $this->_raiseError($msg,$code);
  365.         }
  366.  
  367.         // Get logon greeting/capability and parse
  368.         $this->_parseCapability($res);
  369.  
  370.         if($useTLS === true) {
  371.             // check if we can enable TLS via STARTTLS
  372.             if(isset($this->_capability['starttls']) && function_exists('stream_socket_enable_crypto') === true) {
  373.                 if (PEAR::isError($res = $this->_startTLS())) {
  374.                     return $res;
  375.                 }
  376.             }
  377.         }
  378.  
  379.         return true;
  380.     }
  381.  
  382.     /**
  383.     * Logs into server.
  384.     *
  385.     * @param  string  $user          Login username
  386.     * @param  string  $pass          Login password
  387.     * @param  string  $logintype     Type of login method to use
  388.     * @param  string  $euser         Effective UID (perform on behalf of $euser)
  389.     * @param  boolean $bypassAuth    Do not perform authentication
  390.     * @return mixed                  True on success, PEAR_Error otherwise
  391.     */
  392.     function login($user, $pass, $logintype = null , $euser = '', $bypassAuth = false)
  393.     {
  394.         if (NET_SIEVE_STATE_AUTHORISATION != $this->_state) {
  395.             $msg='Not currently in AUTHORISATION state';
  396.             $code=1;
  397.             return $this->_raiseError($msg,$code);
  398.         }
  399.  
  400.         if( $bypassAuth === false ){
  401.             if(PEAR::isError($res=$this->_cmdAuthenticate($user , $pass , $logintype, $euser ) ) ){
  402.                 return $res;
  403.             }
  404.         }
  405.         $this->_state = NET_SIEVE_STATE_TRANSACTION;
  406.         return true;
  407.     }
  408.  
  409.     /**
  410.      * Handles the authentication using any known method
  411.      *
  412.      * @param string $uid The userid to authenticate as.
  413.      * @param string $pwd The password to authenticate with.
  414.      * @param string $userMethod The method to use ( if $userMethod == '' then the class chooses the best method (the stronger is the best ) )
  415.      * @param string $euser The effective uid to authenticate as.
  416.      *
  417.      * @return mixed  string or PEAR_Error
  418.      *
  419.      * @access private
  420.      * @since  1.0
  421.      */
  422.     function _cmdAuthenticate($uid , $pwd , $userMethod = null , $euser = '' )
  423.     {
  424.         if ( PEAR::isError( $method = $this->_getBestAuthMethod($userMethod) ) ) {
  425.             return $method;
  426.         }
  427.         switch ($method) {
  428.             case 'DIGEST-MD5':
  429.                 $result = $this->_authDigest_MD5( $uid , $pwd , $euser );
  430.                 return $result;
  431.                 break;
  432.             case 'CRAM-MD5':
  433.                 $result = $this->_authCRAM_MD5( $uid , $pwd, $euser);
  434.                 break;
  435.             case 'LOGIN':
  436.                 $result = $this->_authLOGIN( $uid , $pwd , $euser );
  437.                 break;
  438.             case 'PLAIN':
  439.                 $result = $this->_authPLAIN( $uid , $pwd , $euser );
  440.                 break;
  441.             default :
  442.                 $result = new PEAR_Error( "$method is not a supported authentication method" );
  443.                 break;
  444.         }
  445.  
  446.         if (PEAR::isError($res = $this->_doCmd() )) {
  447.             return $res;
  448.         }
  449.         return $result;
  450.     }
  451.  
  452.     /**
  453.      * Authenticates the user using the PLAIN method.
  454.      *
  455.      * @param string $user The userid to authenticate as.
  456.      * @param string $pass The password to authenticate with.
  457.      * @param string $euser The effective uid to authenticate as.
  458.      *
  459.      * @return array Returns an array containing the response
  460.      *
  461.      * @access private
  462.      * @since  1.0
  463.      */
  464.     function _authPLAIN($user, $pass , $euser )
  465.     {
  466.         if ($euser != '') {
  467.             $cmd=sprintf('AUTHENTICATE "PLAIN" "%s"', base64_encode($euser . chr(0) . $user . chr(0) . $pass ) ) ;
  468.         } else {
  469.             $cmd=sprintf('AUTHENTICATE "PLAIN" "%s"', base64_encode( chr(0) . $user . chr(0) . $pass ) );
  470.         }
  471.         return  $this->_sendCmd( $cmd ) ;
  472.     }
  473.  
  474.     /**
  475.      * Authenticates the user using the PLAIN method.
  476.      *
  477.      * @param string $user The userid to authenticate as.
  478.      * @param string $pass The password to authenticate with.
  479.      * @param string $euser The effective uid to authenticate as.
  480.      *
  481.      * @return array Returns an array containing the response
  482.      *
  483.      * @access private
  484.      * @since  1.0
  485.      */
  486.     function _authLOGIN($user, $pass , $euser )
  487.     {
  488.         $this->_sendCmd('AUTHENTICATE "LOGIN"');
  489.         $this->_doCmd(sprintf('"%s"', base64_encode($user)));
  490.         $this->_doCmd(sprintf('"%s"', base64_encode($pass)));
  491.     }
  492.  
  493.     /**
  494.      * Authenticates the user using the CRAM-MD5 method.
  495.      *
  496.      * @param string $uid The userid to authenticate as.
  497.      * @param string $pwd The password to authenticate with.
  498.      * @param string $euser The effective uid to authenticate as.
  499.      *
  500.      * @return array Returns an array containing the response
  501.      *
  502.      * @access private
  503.      * @since  1.0
  504.      */
  505.     function _authCRAM_MD5($uid, $pwd, $euser)
  506.     {
  507.         if ( PEAR::isError( $challenge = $this->_doCmd( 'AUTHENTICATE "CRAM-MD5"' ) ) ) {
  508.             $this->_error=challenge ;
  509.             return challenge ;
  510.         }
  511.         $challenge=trim($challenge);
  512.         $challenge = base64_decode( trim($challenge) );
  513.         $cram = &Auth_SASL::factory('crammd5');
  514.         if ( PEAR::isError($resp=$cram->getResponse( $uid , $pwd , $challenge ) ) ) {
  515.             $this->_error=$resp;
  516.             return $resp;
  517.         }
  518.         $auth_str = base64_encode( $resp );
  519.         if ( PEAR::isError($error = $this->_sendStringResponse( $auth_str  ) ) ) {
  520.             $this->_error=$error;
  521.             return $error;
  522.         }
  523.  
  524.     }
  525.  
  526.     /**
  527.      * Authenticates the user using the DIGEST-MD5 method.
  528.      *
  529.      * @param string $uid The userid to authenticate as.
  530.      * @param string $pwd The password to authenticate with.
  531.      * @param string $euser The effective uid to authenticate as.
  532.      *
  533.      * @return array Returns an array containing the response
  534.      *
  535.      * @access private
  536.      * @since  1.0
  537.      */
  538.     function _authDigest_MD5($uid, $pwd, $euser)
  539.     {
  540.         if ( PEAR::isError( $challenge = $this->_doCmd('AUTHENTICATE "DIGEST-MD5"') ) ) {
  541.             $this->_error=challenge ;
  542.             return challenge ;
  543.         }
  544.         $challenge = base64_decode( $challenge );
  545.         $digest = &Auth_SASL::factory('digestmd5');
  546.  
  547.         if(PEAR::isError($param=$digest->getResponse($uid, $pwd, $challenge, "localhost", "sieve" , $euser) )) {
  548.             return $param;
  549.         }
  550.         $auth_str = base64_encode($param);
  551.  
  552.         if ( PEAR::isError($error = $this->_sendStringResponse( $auth_str  ) ) ) {
  553.             $this->_error=$error;
  554.             return $error;
  555.         }
  556.  
  557.         if ( PEAR::isError( $challenge = $this->_doCmd() ) ) {
  558.             $this->_error=$challenge ;
  559.             return $challenge ;
  560.         }
  561.  
  562.         if( strtoupper(substr($challenge,0,2))== 'OK' ){
  563.                 return true;
  564.         }
  565.  
  566.         /**
  567.         * We don't use the protocol's third step because SIEVE doesn't allow
  568.         * subsequent authentication, so we just silently ignore it.
  569.         */
  570.         if ( PEAR::isError($error = $this->_sendStringResponse( '' ) ) ) {
  571.             $this->_error=$error;
  572.             return $error;
  573.         }
  574.  
  575.         if (PEAR::isError($res = $this->_doCmd() )) {
  576.             return $res;
  577.         }
  578.     }
  579.  
  580.     /**
  581.     * Removes a script from the server
  582.     *
  583.     * @access private
  584.     * @param  string $scriptname Name of the script to delete
  585.     * @return mixed              True on success, PEAR_Error otherwise
  586.     */
  587.     function _cmdDeleteScript($scriptname)
  588.     {
  589.         if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
  590.             $msg='Not currently in AUTHORISATION state';
  591.             $code=1;
  592.             return $this->_raiseError($msg,$code);
  593.         }
  594.         if (PEAR::isError($res = $this->_doCmd(sprintf('DELETESCRIPT "%s"', $scriptname) ) )) {
  595.             return $res;
  596.         }
  597.         return true;
  598.     }
  599.  
  600.     /**
  601.     * Retrieves the contents of the named script
  602.     *
  603.     * @access private
  604.     * @param  string $scriptname Name of the script to retrieve
  605.     * @return mixed              The script if successful, PEAR_Error otherwise
  606.     */
  607.     function _cmdGetScript($scriptname)
  608.     {
  609.         if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
  610.             $msg='Not currently in AUTHORISATION state';
  611.             $code=1;
  612.             return $this->_raiseError($msg,$code);
  613.         }
  614.  
  615.         if (PEAR::isError($res = $this->_doCmd(sprintf('GETSCRIPT "%s"', $scriptname) ) ) ) {
  616.             return $res;
  617.         }
  618.  
  619.         return preg_replace('/{[0-9]+}\r\n/', '', $res);
  620.     }
  621.  
  622.     /**
  623.     * Sets the ACTIVE script, ie the one that gets run on new mail
  624.     * by the server
  625.     *
  626.     * @access private
  627.     * @param  string $scriptname The name of the script to mark as active
  628.     * @return mixed              True on success, PEAR_Error otherwise
  629.     */
  630.     function _cmdSetActive($scriptname)
  631.     {
  632.         if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
  633.             $msg='Not currently in AUTHORISATION state';
  634.             $code=1;
  635.             return $this->_raiseError($msg,$code);
  636.         }
  637.  
  638.         if (PEAR::isError($res = $this->_doCmd(sprintf('SETACTIVE "%s"', $scriptname) ) ) ) {
  639.             return $res;
  640.         }
  641.  
  642.         $this->_activeScript = $scriptname;
  643.         return true;
  644.     }
  645.  
  646.     /**
  647.     * Sends the LISTSCRIPTS command
  648.     *
  649.     * @access private
  650.     * @return mixed Two item array of scripts, and active script on success,
  651.     *               PEAR_Error otherwise.
  652.     */
  653.     function _cmdListScripts()
  654.     {
  655.         if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
  656.             $msg='Not currently in AUTHORISATION state';
  657.             $code=1;
  658.             return $this->_raiseError($msg,$code);
  659.         }
  660.  
  661.         $scripts = array();
  662.         $activescript = null;
  663.  
  664.         if (PEAR::isError($res = $this->_doCmd('LISTSCRIPTS'))) {
  665.             return $res;
  666.         }
  667.  
  668.         $res = explode("\r\n", $res);
  669.  
  670.         foreach ($res as $value) {
  671.             if (preg_match('/^"(.*)"( ACTIVE)?$/i', $value, $matches)) {
  672.                 $scripts[] = $matches[1];
  673.                 if (!empty($matches[2])) {
  674.                     $activescript = $matches[1];
  675.                 }
  676.             }
  677.         }
  678.  
  679.         return array($scripts, $activescript);
  680.     }
  681.  
  682.     /**
  683.     * Sends the PUTSCRIPT command to add a script to
  684.     * the server.
  685.     *
  686.     * @access private
  687.     * @param  string $scriptname Name of the new script
  688.     * @param  string $scriptdata The new script
  689.     * @return mixed              True on success, PEAR_Error otherwise
  690.     */
  691.     function _cmdPutScript($scriptname, $scriptdata)
  692.     {
  693.         if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
  694.             $msg='Not currently in TRANSACTION state';
  695.             $code=1;
  696.             return $this->_raiseError($msg,$code);
  697.         }
  698.  
  699.         $stringLength = $this->_getLineLength($scriptdata);
  700.  
  701.         if (PEAR::isError($res = $this->_doCmd(sprintf("PUTSCRIPT \"%s\" {%d+}\r\n%s", $scriptname, $stringLength, $scriptdata) ))) {
  702.             return $res;
  703.         }
  704.  
  705.         return true;
  706.     }
  707.  
  708.     /**
  709.     * Sends the LOGOUT command and terminates the connection
  710.     *
  711.     * @access private
  712.     * @param boolean $sendLogoutCMD True to send LOGOUT command before disconnecting
  713.     * @return mixed True on success, PEAR_Error otherwise
  714.     */
  715.     function _cmdLogout($sendLogoutCMD=true)
  716.     {
  717.         if (NET_SIEVE_STATE_DISCONNECTED === $this->_state) {
  718.             $msg='Not currently connected';
  719.             $code=1;
  720.             return $this->_raiseError($msg,$code);
  721.             //return PEAR::raiseError('Not currently connected');
  722.         }
  723.  
  724.         if($sendLogoutCMD){
  725.             if (PEAR::isError($res = $this->_doCmd('LOGOUT'))) {
  726.                 return $res;
  727.             }
  728.         }
  729.  
  730.         $this->_sock->disconnect();
  731.         $this->_state = NET_SIEVE_STATE_DISCONNECTED;
  732.         return true;
  733.     }
  734.  
  735.     /**
  736.     * Sends the CAPABILITY command
  737.     *
  738.     * @access private
  739.     * @return mixed True on success, PEAR_Error otherwise
  740.     */
  741.     function _cmdCapability()
  742.     {
  743.         if (NET_SIEVE_STATE_DISCONNECTED === $this->_state) {
  744.             $msg='Not currently connected';
  745.             $code=1;
  746.             return $this->_raiseError($msg,$code);
  747.         }
  748.  
  749.         if (PEAR::isError($res = $this->_doCmd('CAPABILITY'))) {
  750.             return $res;
  751.         }
  752.         $this->_parseCapability($res);
  753.         return true;
  754.     }
  755.  
  756.     /**
  757.     * Checks if the server has space to store the script
  758.     * by the server
  759.     *
  760.     * @param  string $scriptname The name of the script to mark as active
  761.     * @param integer $size The size of the script
  762.     * @return mixed              True on success, PEAR_Error otherwise
  763.     */
  764.     function haveSpace($scriptname,$size)
  765.     {
  766.         if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
  767.             $msg='Not currently in TRANSACTION state';
  768.             $code=1;
  769.             return $this->_raiseError($msg,$code);
  770.         }
  771.  
  772.         if (PEAR::isError($res = $this->_doCmd(sprintf('HAVESPACE "%s" %d', $scriptname, $size) ) ) ) {
  773.             return $res;
  774.         }
  775.  
  776.         return true;
  777.     }
  778.  
  779.     /**
  780.     * Parses the response from the capability command. Stores
  781.     * the result in $this->_capability
  782.     *
  783.     * @access private
  784.     * @param string $data The response from the capability command
  785.     */
  786.     function _parseCapability($data)
  787.     {
  788.         $data = preg_split('/\r?\n/', $data, -1, PREG_SPLIT_NO_EMPTY);
  789.  
  790.         for ($i = 0; $i < count($data); $i++) {
  791.             if (preg_match('/^"([a-z]+)"( "(.*)")?$/i', $data[$i], $matches)) {
  792.                 switch (strtolower($matches[1])) {
  793.                     case 'implementation':
  794.                         $this->_capability['implementation'] = $matches[3];
  795.                         break;
  796.  
  797.                     case 'sasl':
  798.                         $this->_capability['sasl'] = preg_split('/\s+/', $matches[3]);
  799.                         break;
  800.  
  801.                     case 'sieve':
  802.                         $this->_capability['extensions'] = preg_split('/\s+/', $matches[3]);
  803.                         break;
  804.  
  805.                     case 'starttls':
  806.                         $this->_capability['starttls'] = true;
  807.                         break;
  808.                 }
  809.             }
  810.         }
  811.     }
  812.  
  813.     /**
  814.     * Sends a command to the server
  815.     *
  816.     * @access private
  817.     * @param string $cmd The command to send
  818.     */
  819.     function _sendCmd($cmd)
  820.     {
  821.         $status = $this->_sock->getStatus();
  822.         if (PEAR::isError($status) || $status['eof']) {
  823.             return new PEAR_Error( 'Failed to write to socket: (connection lost!) ' );
  824.         }
  825.         if ( PEAR::isError( $error = $this->_sock->write( $cmd . "\r\n" ) ) ) {
  826.             return new PEAR_Error( 'Failed to write to socket: ' . $error->getMessage() );
  827.         }
  828.  
  829.         if( $this->_debug ){
  830.             // C: means this data was sent by  the client (this class)
  831.             echo "C:$cmd\n";
  832.         }
  833.         return true;
  834.     }
  835.  
  836.     /**
  837.     * Sends a string response to the server
  838.     *
  839.     * @access private
  840.     * @param string $cmd The command to send
  841.     */
  842.     function _sendStringResponse($str)
  843.     {
  844.         $response='{' .  $this->_getLineLength($str) . "+}\r\n" . $str  ;
  845.         return $this->_sendCmd($response);
  846.     }
  847.  
  848.     function _recvLn()
  849.     {
  850.         $lastline='';
  851.         if (PEAR::isError( $lastline = $this->_sock->gets( 8192 ) ) ) {
  852.             return new PEAR_Error( 'Failed to write to socket: ' . $lastline->getMessage() );
  853.         }
  854.         $lastline=rtrim($lastline);
  855.         if($this->_debug){
  856.             // S: means this data was sent by  the IMAP Server
  857.             echo "S:$lastline\n" ;
  858.         }
  859.  
  860.         if( $lastline === '' ) {
  861.             return new PEAR_Error( 'Failed to receive from the socket' );
  862.         }
  863.  
  864.         return $lastline;
  865.     }
  866.  
  867.     /**
  868.     * Send a command and retrieves a response from the server.
  869.     *
  870.     *
  871.     * @access private
  872.     * @param string $cmd The command to send
  873.     * @return mixed Reponse string if an OK response, PEAR_Error if a NO response
  874.     */
  875.     function _doCmd($cmd = '' )
  876.     {
  877.         $referralCount=0;
  878.         while($referralCount < $this->_maxReferralCount ){
  879.  
  880.             if($cmd != '' ){
  881.                 if(PEAR::isError($error = $this->_sendCmd($cmd) )) {
  882.                     return $error;
  883.                 }
  884.             }
  885.             $response = '';
  886.  
  887.             while (true) {
  888.                     if(PEAR::isError( $line=$this->_recvLn() )){
  889.                         return $line;
  890.                     }
  891.                     if ('ok' === strtolower(substr($line, 0, 2))) {
  892.                         $response .= $line;
  893.                         return rtrim($response);
  894.  
  895.                     } elseif ('no' === strtolower(substr($line, 0, 2))) {
  896.                         // Check for string literal error message
  897.                         if (preg_match('/^no {([0-9]+)\+?}/i', $line, $matches)) {
  898.                             $line .= str_replace("\r\n", ' ', $this->_sock->read($matches[1] + 2 ));
  899.                             if($this->_debug){
  900.                                 echo "S:$line\n";
  901.                             }
  902.                         }
  903.                         $msg=trim($response . substr($line, 2));
  904.                         $code=3;
  905.                         return $this->_raiseError($msg,$code);
  906.                     } elseif ('bye' === strtolower(substr($line, 0, 3))) {
  907.  
  908.                         if(PEAR::isError($error = $this->disconnect(false) ) ){
  909.                             $msg="Can't handle bye, The error was= " . $error->getMessage() ;
  910.                             $code=4;
  911.                             return $this->_raiseError($msg,$code);
  912.                         }
  913.                         //if (preg_match('/^bye \(referral "([^"]+)/i', $line, $matches)) {
  914.                         if (preg_match('/^bye \(referral "(sieve:\/\/)?([^"]+)/i', $line, $matches)) {
  915.                             // Check for referral, then follow it.  Otherwise, carp an error.
  916.                             // Replace the old host with the referral host preserving any protocol prefix
  917.                             $this->_data['host'] = preg_replace('/\w+(?!(\w|\:\/\/)).*/',$matches[2],$this->_data['host']);
  918.                            if (PEAR::isError($error = $this->_handleConnectAndLogin() ) ){
  919.                                 $msg="Can't follow referral to " . $this->_data['host'] . ", The error was= " . $error->getMessage() ;
  920.                                 $code=5;
  921.                                 return $this->_raiseError($msg,$code);
  922.                             }
  923.                             break;
  924.                             // Retry the command
  925.                             if(PEAR::isError($error = $this->_sendCmd($cmd) )) {
  926.                                 return $error;
  927.                             }
  928.                             continue;
  929.                         }
  930.                         $msg=trim($response . $line);
  931.                         $code=6;
  932.                         return $this->_raiseError($msg,$code);
  933.                     } elseif (preg_match('/^{([0-9]+)\+?}/i', $line, $matches)) {
  934.                         // Matches String Responses.
  935.                         //$line = str_replace("\r\n", ' ', $this->_sock->read($matches[1] + 2 ));
  936.                         $str_size = $matches[1] + 2;
  937.                         $line = '';
  938.                         $line_length = 0;
  939.                         while ($line_length < $str_size) {
  940.                             $line .= $this->_sock->read($str_size - $line_length);
  941.                             $line_length = $this->_getLineLength($line);
  942.                         }
  943.                         if($this->_debug){
  944.                             echo "S:$line\n";
  945.                         }
  946.                         // receive the pending OK
  947.                         $this->_recvLn();
  948.                         return $line;
  949.                     }
  950.                     $response .= $line . "\r\n";
  951.                     $referralCount++;
  952.                 }
  953.         }
  954.         $msg="Max referral count reached ($referralCount times) Cyrus murder loop error?";
  955.         $code=7;
  956.         return $this->_raiseError($msg,$code);
  957.     }
  958.  
  959.     /**
  960.     * Sets the debug state
  961.     *
  962.     * @param boolean $debug
  963.     * @return void
  964.     */
  965.     function setDebug($debug = true)
  966.     {
  967.         $this->_debug = $debug;
  968.     }
  969.  
  970.     /**
  971.     * Disconnect from the Sieve server
  972.     *
  973.     * @param  string $scriptname The name of the script to be set as active
  974.     * @return mixed              true on success, PEAR_Error on failure
  975.     */
  976.     function disconnect($sendLogoutCMD=true)
  977.     {
  978.         return $this->_cmdLogout($sendLogoutCMD);
  979.     }
  980.  
  981.     /**
  982.      * Returns the name of the best authentication method that the server
  983.      * has advertised.
  984.      *
  985.      * @param string if !=null,authenticate with this method ($userMethod).
  986.      *
  987.      * @return mixed    Returns a string containing the name of the best
  988.      *                  supported authentication method or a PEAR_Error object
  989.      *                  if a failure condition is encountered.
  990.      * @access private
  991.      * @since  1.0
  992.      */
  993.     function _getBestAuthMethod($userMethod = null)
  994.     {
  995.        if( isset($this->_capability['sasl']) ){
  996.            $serverMethods=$this->_capability['sasl'];
  997.        }else{
  998.            // if the server don't send an sasl capability fallback to login auth
  999.            //return 'LOGIN';
  1000.            return new PEAR_Error("This server don't support any Auth methods SASL problem?");
  1001.        }
  1002.  
  1003.         if($userMethod != null ){
  1004.             $methods = array();
  1005.             $methods[] = $userMethod;
  1006.         }else{
  1007.  
  1008.             $methods = $this->supportedAuthMethods;
  1009.         }
  1010.         if( ($methods != null) && ($serverMethods != null)){
  1011.             foreach ( $methods as $method ) {
  1012.                 if ( in_array( $method , $serverMethods ) ) {
  1013.                     return $method;
  1014.                 }
  1015.             }
  1016.             $serverMethods=implode(',' , $serverMethods );
  1017.             $myMethods=implode(',' ,$this->supportedAuthMethods);
  1018.             return new PEAR_Error("$method NOT supported authentication method!. This server " .
  1019.                 "supports these methods= $serverMethods, but I support $myMethods");
  1020.         }else{
  1021.             return new PEAR_Error("This server don't support any Auth methods");
  1022.         }
  1023.     }
  1024.  
  1025.     /**
  1026.     * Return the list of extensions the server supports
  1027.     *
  1028.     * @return mixed              array  on success, PEAR_Error on failure
  1029.     */
  1030.     function getExtensions()
  1031.     {
  1032.         if (NET_SIEVE_STATE_DISCONNECTED === $this->_state) {
  1033.             $msg='Not currently connected';
  1034.             $code=7;
  1035.             return $this->_raiseError($msg,$code);
  1036.         }
  1037.  
  1038.         return $this->_capability['extensions'];
  1039.     }
  1040.  
  1041.     /**
  1042.     * Return true if tyhe server has that extension
  1043.     *
  1044.     * @param string  the extension to compare
  1045.     * @return mixed              array  on success, PEAR_Error on failure
  1046.     */
  1047.     function hasExtension($extension)
  1048.     {
  1049.         if (NET_SIEVE_STATE_DISCONNECTED === $this->_state) {
  1050.             $msg='Not currently connected';
  1051.             $code=7;
  1052.             return $this->_raiseError($msg,$code);
  1053.         }
  1054.  
  1055.         if(is_array($this->_capability['extensions'] ) ){
  1056.             foreach( $this->_capability['extensions'] as $ext){
  1057.                 if( trim( strtolower( $ext ) ) === trim( strtolower( $extension ) ) )
  1058.                     return true;
  1059.             }
  1060.         }
  1061.         return false;
  1062.     }
  1063.  
  1064.     /**
  1065.     * Return the list of auth methods the server supports
  1066.     *
  1067.     * @return mixed              array  on success, PEAR_Error on failure
  1068.     */
  1069.     function getAuthMechs()
  1070.     {
  1071.         if (NET_SIEVE_STATE_DISCONNECTED === $this->_state) {
  1072.             $msg='Not currently connected';
  1073.             $code=7;
  1074.             return $this->_raiseError($msg,$code);
  1075.         }
  1076.         if(!isset($this->_capability['sasl']) ){
  1077.             $this->_capability['sasl']=array();
  1078.         }
  1079.         return $this->_capability['sasl'];
  1080.     }
  1081.  
  1082.     /**
  1083.     * Return true if the server has that extension
  1084.     *
  1085.     * @param string  the extension to compare
  1086.     * @return mixed              array  on success, PEAR_Error on failure
  1087.     */
  1088.     function hasAuthMech($method)
  1089.     {
  1090.         if (NET_SIEVE_STATE_DISCONNECTED === $this->_state) {
  1091.             $msg='Not currently connected';
  1092.             $code=7;
  1093.             return $this->_raiseError($msg,$code);
  1094.             //return PEAR::raiseError('Not currently connected');
  1095.         }
  1096.  
  1097.         if(is_array($this->_capability['sasl'] ) ){
  1098.             foreach( $this->_capability['sasl'] as $ext){
  1099.                 if( trim( strtolower( $ext ) ) === trim( strtolower( $method ) ) )
  1100.                     return true;
  1101.             }
  1102.         }
  1103.         return false;
  1104.     }
  1105.  
  1106.     /**
  1107.     * Return true if the TLS negotiation was successful
  1108.     *
  1109.     * @access private
  1110.     * @return mixed              true on success, PEAR_Error on failure
  1111.     */
  1112.     function _startTLS()
  1113.     {
  1114.         if (PEAR::isError($res = $this->_doCmd("STARTTLS"))) {
  1115.             return $res;
  1116.         }
  1117.  
  1118.         if(stream_socket_enable_crypto($this->_sock->fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT) == false) {
  1119.             $msg='Failed to establish TLS connection';
  1120.             $code=2;
  1121.             return $this->_raiseError($msg,$code);
  1122.         }
  1123.  
  1124.         if($this->_debug === true) {
  1125.             echo "STARTTLS Negotiation Successful\n";
  1126.         }
  1127.  
  1128.         // RFC says we need to query the server capabilities again
  1129.         if(PEAR::isError($res = $this->_cmdCapability() )) {
  1130.             $msg='Failed to connect, server said: ' . $res->getMessage();
  1131.             $code=2;
  1132.             return $this->_raiseError($msg,$code);
  1133.         }
  1134.         return true;
  1135.     }
  1136.  
  1137.     function _getLineLength($string) {
  1138.         if (extension_loaded('mbstring') || @dl(PHP_SHLIB_PREFIX.'mbstring.'.PHP_SHLIB_SUFFIX)) {
  1139.           return mb_strlen($string,'latin1');
  1140.         } else {
  1141.           return strlen($string);
  1142.         }
  1143.     }
  1144. }
  1145. ?>
  1146.